﻿#region Header

// <file>
//     <copyright see="prj:///doc/copyright.txt"/>
//     <license see="prj:///doc/license.txt"/>
//     <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
//     <version>$Revision: 2691 $</version>
// </file>

#endregion Header

namespace ICSharpCode.TextEditor.Document
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics;

    using ICSharpCode.TextEditor.Undo;

    #region Enumerations

    /// <summary>
    /// Describes the bracket highlighting style
    /// </summary>
    public enum BracketHighlightingStyle
    {
        /// <summary>
        /// Brackets won't be highlighted
        /// </summary>
        None,

        /// <summary>
        /// Brackets will be highlighted if the caret is on the bracket
        /// </summary>
        OnBracket,

        /// <summary>
        /// Brackets will be highlighted if the caret is after the bracket
        /// </summary>
        AfterBracket
    }

    /// <summary>
    /// Describes the selection mode of the text area
    /// </summary>
    public enum DocumentSelectionMode
    {
        /// <summary>
        /// The 'normal' selection mode.
        /// </summary>
        Normal,

        /// <summary>
        /// Selections will be added to the current selection or new
        /// ones will be created (multi-select mode)
        /// </summary>
        Additive
    }

    /// <summary>
    /// Describes the indent style
    /// </summary>
    public enum IndentStyle
    {
        /// <summary>
        /// No indentation occurs
        /// </summary>
        None,

        /// <summary>
        /// The indentation from the line above will be
        /// taken to indent the curent line
        /// </summary>
        Auto,

        /// <summary>
        /// Inteligent, context sensitive indentation will occur
        /// </summary>
        Smart
    }

    /// <summary>
    /// Describes the caret marker
    /// </summary>
    public enum LineViewerStyle
    {
        /// <summary>
        /// No line viewer will be displayed
        /// </summary>
        None,

        /// <summary>
        /// The row in which the caret is will be marked
        /// </summary>
        FullRow
    }

    #endregion Enumerations

    /// <summary>
    /// The default <see cref="IDocument"/> implementation.
    /// </summary>
    internal sealed class DefaultDocument : IDocument
    {
        #region Fields

        BookmarkManager bookmarkManager;
        ICustomLineManager customLineManager;
        FoldingManager foldingManager;
        IFormattingStrategy formattingStrategy;
        LineManager lineTrackingStrategy;
        MarkerStrategy markerStrategy;
        bool readOnly = false;
        ITextBufferStrategy textBufferStrategy;
        ITextEditorProperties textEditorProperties = new DefaultTextEditorProperties();
        UndoStack undoStack = new UndoStack();

        // UPDATE STUFF
        List<TextAreaUpdate> updateQueue = new List<TextAreaUpdate>();

        #endregion Fields

        #region Events

        public event DocumentEventHandler DocumentAboutToBeChanged;

        public event DocumentEventHandler DocumentChanged;

        public event EventHandler<LineCountChangeEventArgs> LineCountChanged
        {
            add { lineTrackingStrategy.LineCountChanged += value; }
            remove { lineTrackingStrategy.LineCountChanged -= value; }
        }

        public event EventHandler<LineEventArgs> LineDeleted
        {
            add { lineTrackingStrategy.LineDeleted += value; }
            remove { lineTrackingStrategy.LineDeleted -= value; }
        }

        public event EventHandler<LineLengthChangeEventArgs> LineLengthChanged
        {
            add { lineTrackingStrategy.LineLengthChanged += value; }
            remove { lineTrackingStrategy.LineLengthChanged -= value; }
        }

        public event EventHandler TextContentChanged;

        public event EventHandler UpdateCommited;

        #endregion Events

        #region Properties

        public BookmarkManager BookmarkManager
        {
            get {
                return bookmarkManager;
            }
            set {
                bookmarkManager = value;
            }
        }

        public ICustomLineManager CustomLineManager
        {
            get {
                return customLineManager;
            }
            set {
                customLineManager = value;
            }
        }

        public FoldingManager FoldingManager
        {
            get {
                return foldingManager;
            }
            set {
                foldingManager = value;
            }
        }

        public IFormattingStrategy FormattingStrategy
        {
            get {
                return formattingStrategy;
            }
            set {
                formattingStrategy = value;
            }
        }

        public IHighlightingStrategy HighlightingStrategy
        {
            get {
                return lineTrackingStrategy.HighlightingStrategy;
            }
            set {
                lineTrackingStrategy.HighlightingStrategy = value;
            }
        }

        public LineManager LineManager
        {
            get { return lineTrackingStrategy; }
            set { lineTrackingStrategy = value; }
        }

        public IList<LineSegment> LineSegmentCollection
        {
            get {
                return lineTrackingStrategy.LineSegmentCollection;
            }
        }

        public MarkerStrategy MarkerStrategy
        {
            get { return markerStrategy; }
            set { markerStrategy = value; }
        }

        public bool ReadOnly
        {
            get {
                return readOnly;
            }
            set {
                readOnly = value;
            }
        }

        public ITextBufferStrategy TextBufferStrategy
        {
            get {
                return textBufferStrategy;
            }
            set {
                textBufferStrategy = value;
            }
        }

        public string TextContent
        {
            get {
                return GetText(0, textBufferStrategy.Length);
            }
            set {
                Debug.Assert(textBufferStrategy != null);
                Debug.Assert(lineTrackingStrategy != null);
                OnDocumentAboutToBeChanged(new DocumentEventArgs(this, 0, 0, value));
                textBufferStrategy.SetContent(value);
                lineTrackingStrategy.SetContent(value);
                undoStack.ClearAll();

                OnDocumentChanged(new DocumentEventArgs(this, 0, 0, value));
                OnTextContentChanged(EventArgs.Empty);
            }
        }

        public ITextEditorProperties TextEditorProperties
        {
            get {
                return textEditorProperties;
            }
            set {
                textEditorProperties = value;
            }
        }

        public int TextLength
        {
            get {
                return textBufferStrategy.Length;
            }
        }

        public int TotalNumberOfLines
        {
            get {
                return lineTrackingStrategy.TotalNumberOfLines;
            }
        }

        public UndoStack UndoStack
        {
            get {
                return undoStack;
            }
        }

        public List<TextAreaUpdate> UpdateQueue
        {
            get {
                return updateQueue;
            }
        }

        #endregion Properties

        #region Methods

        public void CommitUpdate()
        {
            if (UpdateCommited != null) {
                UpdateCommited(this, EventArgs.Empty);
            }
        }

        public char GetCharAt(int offset)
        {
            return textBufferStrategy.GetCharAt(offset);
        }

        public int GetFirstLogicalLine(int lineNumber)
        {
            return lineTrackingStrategy.GetFirstLogicalLine(lineNumber);
        }

        public int GetLastLogicalLine(int lineNumber)
        {
            return lineTrackingStrategy.GetLastLogicalLine(lineNumber);
        }

        public int GetLineNumberForOffset(int offset)
        {
            return lineTrackingStrategy.GetLineNumberForOffset(offset);
        }

        public LineSegment GetLineSegment(int line)
        {
            return lineTrackingStrategy.GetLineSegment(line);
        }

        public LineSegment GetLineSegmentForOffset(int offset)
        {
            return lineTrackingStrategy.GetLineSegmentForOffset(offset);
        }

        //        public int GetVisibleColumn(int logicalLine, int logicalColumn)
        //        {
        //            return lineTrackingStrategy.GetVisibleColumn(logicalLine, logicalColumn);
        //        }
        //
        public int GetNextVisibleLineAbove(int lineNumber, int lineCount)
        {
            return lineTrackingStrategy.GetNextVisibleLineAbove(lineNumber, lineCount);
        }

        public int GetNextVisibleLineBelow(int lineNumber, int lineCount)
        {
            return lineTrackingStrategy.GetNextVisibleLineBelow(lineNumber, lineCount);
        }

        public string GetText(int offset, int length)
        {
            #if DEBUG
            if (length < 0) throw new ArgumentOutOfRangeException("length", length, "length < 0");
            #endif
            return textBufferStrategy.GetText(offset, length);
        }

        public string GetText(ISegment segment)
        {
            return GetText(segment.Offset, segment.Length);
        }

        public int GetVisibleLine(int lineNumber)
        {
            return lineTrackingStrategy.GetVisibleLine(lineNumber);
        }

        public void Insert(int offset, string text)
        {
            if (readOnly) {
                return;
            }
            OnDocumentAboutToBeChanged(new DocumentEventArgs(this, offset, -1, text));

            textBufferStrategy.Insert(offset, text);
            lineTrackingStrategy.Insert(offset, text);

            undoStack.Push(new UndoableInsert(this, offset, text));

            OnDocumentChanged(new DocumentEventArgs(this, offset, -1, text));
        }

        public TextLocation OffsetToPosition(int offset)
        {
            int lineNr = GetLineNumberForOffset(offset);
            LineSegment line = GetLineSegment(lineNr);
            return new TextLocation(offset - line.Offset, lineNr);
        }

        public int PositionToOffset(TextLocation p)
        {
            if (p.Y >= this.TotalNumberOfLines) {
                return 0;
            }
            LineSegment line = GetLineSegment(p.Y);
            return Math.Min(this.TextLength, line.Offset + Math.Min(line.Length, p.X));
        }

        public void Remove(int offset, int length)
        {
            if (readOnly) {
                return;
            }
            OnDocumentAboutToBeChanged(new DocumentEventArgs(this, offset, length));
            undoStack.Push(new UndoableDelete(this, offset, GetText(offset, length)));

            textBufferStrategy.Remove(offset, length);
            lineTrackingStrategy.Remove(offset, length);

            OnDocumentChanged(new DocumentEventArgs(this, offset, length));
        }

        public void Replace(int offset, int length, string text)
        {
            if (readOnly) {
                return;
            }
            OnDocumentAboutToBeChanged(new DocumentEventArgs(this, offset, length, text));
            undoStack.Push(new UndoableReplace(this, offset, GetText(offset, length), text));

            textBufferStrategy.Replace(offset, length, text);
            lineTrackingStrategy.Replace(offset, length, text);

            OnDocumentChanged(new DocumentEventArgs(this, offset, length, text));
        }

        public void RequestUpdate(TextAreaUpdate update)
        {
            if (updateQueue.Count == 1 && updateQueue[0].TextAreaUpdateType == TextAreaUpdateType.WholeTextArea) {
                // if we're going to update the whole text area, we don't need to store detail updates
                return;
            }
            if (update.TextAreaUpdateType == TextAreaUpdateType.WholeTextArea) {
                // if we're going to update the whole text area, we don't need to store detail updates
                updateQueue.Clear();
            }
            updateQueue.Add(update);
        }

        public void UpdateSegmentListOnDocumentChange<T>(List<T> list, DocumentEventArgs e)
            where T : ISegment
        {
            for (int i = 0; i < list.Count; ++i) {
                ISegment fm = list[i];

                if (e.Offset <= fm.Offset && fm.Offset <= e.Offset + e.Length ||
                    e.Offset <= fm.Offset + fm.Length && fm.Offset + fm.Length <= e.Offset + e.Length) {
                    list.RemoveAt(i);
                    --i;
                    continue;
                }

                if (fm.Offset  <= e.Offset && e.Offset <= fm.Offset + fm.Length) {
                    if (e.Text != null) {
                        fm.Length += e.Text.Length;
                    }
                    if (e.Length > 0) {
                        fm.Length -= e.Length;
                    }
                    continue;
                }

                if (fm.Offset >= e.Offset) {
                    if (e.Text != null) {
                        fm.Offset += e.Text.Length;
                    }
                    if (e.Length > 0) {
                        fm.Offset -= e.Length;
                    }
                }
            }
        }

        [Conditional("DEBUG")]
        internal static void ValidatePosition(IDocument document, TextLocation position)
        {
            document.GetLineSegment(position.Line);
        }

        void OnDocumentAboutToBeChanged(DocumentEventArgs e)
        {
            if (DocumentAboutToBeChanged != null) {
                DocumentAboutToBeChanged(this, e);
            }
        }

        void OnDocumentChanged(DocumentEventArgs e)
        {
            if (DocumentChanged != null) {
                DocumentChanged(this, e);
            }
        }

        void OnTextContentChanged(EventArgs e)
        {
            if (TextContentChanged != null) {
                TextContentChanged(this, e);
            }
        }

        #endregion Methods
    }
}